package com.dz.finance.common.service.impl;

import com.dz.finance.common.constant.SystemConstant;
import com.dz.finance.common.service.UserTokenService;
import com.dz.finance.common.config.SecurityConfig;
import com.dz.finance.common.entity.BaseUserInfo;
import com.dz.finance.common.entity.Token;
import com.dz.finance.common.exception.BizException;
import com.dz.finance.common.exception.LoginException;
import com.dz.finance.common.utils.DateUtils;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * @author: DengHaoQi
 * @date: 2024/6/4 17:09
 * @description:
 */

@Component
@RequiredArgsConstructor
@ConditionalOnProperty(prefix = "sys", name = "enable-my-security", havingValue = "true") // 条件满足才自动装配这个类
public class UserTokenServiceImpl<T extends BaseUserInfo> implements UserTokenService<T> {

    ThreadLocal<T> userThreadLocal;
    final RedisTemplate<String, T> redisTemplate;
    final SecurityConfig securityConfig;


    /*
     * 为user构造token
     * */
    @Override
    public T buildToken(T user) {
        if (user == null) {
            throw new LoginException("用户信息不能为空");
        }
        Token token = new Token();
        token.setToken(UUID.randomUUID().toString());
        token.setExpire(securityConfig.getExpire());
        token.setExpireDateTime(DateUtils.getExpireTimeStamp(token.getExpire(), TimeUnit.DAYS));
        token.setTimeUnit(TimeUnit.DAYS);
        Object tokenByCache = redisTemplate.opsForValue().get(SystemConstant.TOKEN_CACHE_KEY + token.getToken());
        if (Objects.nonNull(tokenByCache)) {
            // 小概率事件 token重复
            throw new BizException("token已存在，不能登录或注册");
        }
        user.setToken(token);
        return user;
    }


    /*
     * 校验token是否合法
     * */
    @Override
    public T checkToken(String token) {
        Assert.hasText(token, "token不存在!");
        T user = redisTemplate.opsForValue().get(SystemConstant.TOKEN_CACHE_KEY + token);

        if (Objects.isNull(user)) {
            throw new LoginException("token已过期");
        }

        // 是否刷新token时间
        boolean isRefresh = isRefreshToken(user.getToken());
        if (isRefresh) {
            // 刷新token,重置过期时间
            user.getToken().setExpireDateTime(DateUtils.getExpireTimeStamp(user.getToken().getExpire(),TimeUnit.DAYS));
            redisTemplate.opsForValue().set(SystemConstant.TOKEN_CACHE_KEY + user.getToken().getToken(),
                    user,
                    user.getToken().getExpire(),
                    user.getToken().getTimeUnit());
        }
        return user;
    }

    /*
    * 将用户信息存储在ThreadLocal
    * */
    @Override
    public void setThreadLocalUser(T user) {
        if (Objects.isNull(userThreadLocal)) {
            userThreadLocal = new ThreadLocal<>();
        }
        userThreadLocal.set(user);
    }


    /*
     * 从ThreadLocal中获取登陆用户
     * */
    @Override
    public T getThreadLocalUser() {
        if (userThreadLocal == null) {
            return null;
        }
        return userThreadLocal.get();
    }


    /*
     * 获取ThreadLocal中的用户ID
     * */
    @Override
    public Long getThreadLocalUserId() {
        T user = getThreadLocalUser();
        if (Objects.nonNull(user) && user.getId() != null && user.getId() > 0) {
            return user.getId();
        }
        throw new LoginException("获取用户id异常");
    }

    /*
    * 将用户信息删除
    * */
    @Override
    public void removeThreadLocalUser() {
        if (Objects.nonNull(userThreadLocal)) {
            userThreadLocal.remove();
        }
    }


    /*
     * 根据token过期时间判断是否需要刷新token
     * */
    private boolean isRefreshToken(Token token) {
        if (Objects.isNull(token)) {
            throw new BizException("Token不存在");
        }

        //求token已经使用了多长时间
        long expireTime = token.getExpireDateTime() - DateUtils.getSecondTimeStamp();
        Assert.isTrue(!(expireTime <= 0), "token已过期");
        long useTime = token.getExpire() - expireTime;
        //token使用时间超过三分之一则刷新
        return useTime > (token.getExpire() / 3);
    }
}
